package ldk.util.logger;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.util.Log;

import java.io.File;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;


/**
 * A simple wrapper for log 2 file on Android
 * using java.util.logger function
 * API just like Android's Log
 * Created by ldkxingzhe@163.com on 2017/5/26.
 */
public class LLog {
    @VisibleForTesting static LLog sInstance;

    private Set<Handler> mHandler = new HashSet<>();
    private Executor mExecutor = Executors.newCachedThreadPool();

    @VisibleForTesting static LLog getInstance(){
        if (sInstance == null){
            synchronized (LLog.class){
                if (sInstance == null){
                    sInstance = new LLog();
                }
            }
        }
        return sInstance;
    }

    public static void v(final String tag, final String message, final Object... objects){
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                log(Log.VERBOSE, tag, message, objects);
            }
        };
        runBackground(runnable);
    }

    public static void d(final String tag, final String message, final Object... objects){
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                log(Log.DEBUG, tag, message, objects);
            }
        };
        runBackground(runnable);
    }

    public static void i(final String tag, final String message, final Object... objects){
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                log(Log.INFO, tag, message, objects);
            }
        };
        runBackground(runnable);
    }

    public static void w(final String tag, final String message, final Object... objects){
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                log(Log.WARN, tag, message, objects);
            }
        };
        runBackground(runnable);
    }

    public static void e(final String tag, final String message, final Object... objects){
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                log(Log.ERROR, tag, message, objects);
            }
        };
        runBackground(runnable);
    }

    public static void e(String tag, String message, Throwable throwable){
        log(Log.ERROR, tag, message, throwable);
    }

    public static void log2Logcat(int logLevel, String tag, String message){
        switch (logLevel){
            case Log.VERBOSE:
                Log.v(tag, message);
                break;
            case Log.DEBUG:
                Log.d(tag, message);
                break;
            case Log.INFO:
                Log.i(tag, message);
                break;
            case Log.ERROR:
                Log.e(tag, message);
                break;
            default:
                Log.e(tag, message);
                break;
        }
    }

    public static void flush(){
        if (sInstance != null){
            sInstance.flushInternal();
        }
    }

    private void flushInternal() {
        for (Handler handler : mHandler){
            if (handler != null) handler.flush();
        }
    }

    private static void runBackground(Runnable runnable){
        if (sInstance == null){
            Log.e("LLog", "必须首先调用init(Context), 并且赋予权限");
            return;
        }
        if (Thread.currentThread() != sInstance.getMainLooperThread()){
            sInstance.mExecutor.execute(runnable);
        }else{
            runnable.run();
        }

    }

    @NonNull @VisibleForTesting
    Thread getMainLooperThread() {
        return Looper.getMainLooper().getThread();
    }

    public static void log(int logLevel, String tag, String message, Object... objects){
        if (sInstance == null){
            Log.e("LLog", "必须首先调用init(Context), 并且赋予权限");
            return;
        }else{
            sInstance.logInternal(logLevel, tag, message, objects);
        }
    }

    @VisibleForTesting void logInternal(int logLevel, String tag, String message, Object... objects) {
        String content;
        try{
            content = String.format(message, objects);
        }catch (Exception e){
            content = message;
        }
        if (content == null) content = "null";
        if (tag == null) tag = "null";
        for (Handler handler : sInstance.mHandler){
            handler.write(logLevel, tag, content);
        }
    }


    public static void init(Context context){
        boolean hasPermission = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
        if (!hasPermission) return;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            hasPermission =
                    context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
            if (!hasPermission){
                Log.e("LLog", "you must has permission permission.WRITE_EXTERNAL_STORAGE");
            }
        }
        if (!hasPermission) return;
        File logDirFile = new File(context.getExternalCacheDir(), "ldk_logger");
        getInstance();
        addHandler(new FileHandler(logDirFile));
    }

    public static void addHandler(Handler handler){
        if (sInstance != null) sInstance.addHandlerInternal(handler);
    }

    public void addHandlerInternal(Handler fileHandler) {
        if (!mHandler.contains(fileHandler)){
            mHandler.add(fileHandler);
            if (!fileHandler.isOpened() && !fileHandler.isClosed()){
                fileHandler.open();
            }
        }
    }

    private static class Cleaner extends Thread{
        @Override
        public void run() {
            synchronized (LLog.class){
                if (sInstance != null){
                    System.out.println("清理所有的Handler");
                    for (Handler handler : sInstance.mHandler){
                        handler.flush();
                        handler.close();
                    }
                }
            }
        }
    }

    static {
        Runtime.getRuntime().addShutdownHook(new Cleaner());
    }
}
